package ai.people.core.nebula.aspect;

import ai.people.core.nebula.entity.NebulaTransaction;
import ai.people.core.nebula.holder.NebulaSessionContextHolder;
import ai.people.core.nebula.util.NebulaQueryUtils;
import com.vesoft.nebula.client.graph.exception.IOErrorException;
import com.vesoft.nebula.client.graph.net.Session;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.List;
import java.util.Objects;

/**
 * @author zhenglin
 * @date 2022/6/25
 */
@Aspect
@Component
public class NebulaAutoReleaseAspect {

    private final Logger logger = LoggerFactory.getLogger(NebulaAutoReleaseAspect.class);

    /**
     * service下的类
     */
    @Pointcut(value = "execution(* *..service..*(..))")
    public void cut() {
    }

    /**
     * 关闭连接
     *
     * @param point 点
     * @return {@link Object}
     * @throws Throwable 异常
     */
    @Around("cut()")
    public Object releaseHook(ProceedingJoinPoint point) throws Throwable {
        Object proceed = point.proceed();
        autoRelease(point);
        return proceed;
    }

    private void autoRelease(ProceedingJoinPoint point) {
        doRelease(point.getSignature(), point.getTarget());
    }

    private void doRelease(Signature point, Object point1) {
        try {
            MethodSignature methodSignature = (MethodSignature) point;
            Method method = methodSignature.getMethod();
            NebulaAutoRelease annotation = method.getAnnotation(NebulaAutoRelease.class);
            if (annotation == null) {
                //获取类上注解
                annotation = point1.getClass().getAnnotation(NebulaAutoRelease.class);
            }
            if (annotation == null) {
                return;
            }
            this.sessionRelease();
        } catch (Exception e) {
            logger.error("nebula auto release catch err");
        }
    }
    private void autoRelease(JoinPoint point) {
        doRelease(point.getSignature(), point.getTarget());
    }


    /**
     * 异常通知
     */
    @AfterThrowing(pointcut = "cut()", throwing = "ex")
    public void exceptionNotification(JoinPoint joinPoint, Exception ex){
        logger.error("business exception notification ", ex);
//        rollback();
        autoRelease(joinPoint);
    }

    private void rollback() {
        try {
            NebulaTransaction tx = NebulaSessionContextHolder.getTx();
            if (tx == null) {
                return;
            }
            List<NebulaTransaction.Gql> gqls = tx.getGqls();
            Session session = tx.getSession();
            if(CollectionUtils.isNotEmpty(gqls)) {
                for (NebulaTransaction.Gql gql : gqls) {
                    String rollbackGql = gql.getRollbackGql();
                    if (StringUtils.isNotBlank(rollbackGql)) {
                        NebulaQueryUtils.execute(session, rollbackGql);
                        logger.info("rollback gql : {}", rollbackGql);
                    }
                }
            }
        } catch (IOErrorException e) {
            logger.error("nebula auto rollback catch err:{}",e.getMessage());
        }
    }


    /**
     * 关闭session客户
     */
    private void sessionRelease() {
        NebulaTransaction tx = NebulaSessionContextHolder.getTx();
        if (tx == null) {
            return;
        }
        Session session = tx.getSession();
        try {
            if (Objects.isNull(session)) {
                return;
            }
            session.release();
            logger.info("session released successfully {}", session);
        } catch (Exception e) {
            logger.error("release session exception {}", e.getMessage());
        } finally {
            NebulaSessionContextHolder.resetHolder();
        }
    }
}
